/*
 * TestFul - http://code.google.com/p/testful/
 * Copyright (C) 2010  Matteo Miraz
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package testful.random;

import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import testful.TestFul;
import testful.coverage.CoverageInformation;
import testful.coverage.CoverageTestExecutor;
import testful.coverage.TrackerDatum;
import testful.model.Operation;
import testful.model.OptimalTestCreator;
import testful.model.ReferenceFactory;
import testful.model.Test;
import testful.model.TestCluster;
import testful.model.TestCoverage;
import testful.model.TestSuite;
import testful.model.executor.TestExecutorInput;
import testful.runner.DataFinder;
import testful.runner.Job;
import testful.runner.RunnerPool;
import testful.utils.ElementManager;
import ec.util.MersenneTwisterFast;

public abstract class RandomTest {
	protected static Logger logger = Logger.getLogger("testful.random");

	protected long start, stop;
	private long numCall;

	protected final TestCluster cluster;
	protected final ReferenceFactory refFactory;

	//AtomicInteger for multiple-thread
	private AtomicInteger testsDone = new AtomicInteger();

	protected final BlockingQueue<Entry<Operation[], Future<ElementManager<String, CoverageInformation>>>> tests = new LinkedBlockingQueue<Entry<Operation[], Future<ElementManager<String, CoverageInformation>>>>();
	private final OptimalTestCreator optimal;
	private final DataFinder finder;
	private final boolean reloadClasses;
	private final TrackerDatum[] data;

	protected final MersenneTwisterFast random;

	protected volatile boolean keepRunning = true;

	public RandomTest(DataFinder finder, boolean reloadClasses,
			TestCluster cluster, ReferenceFactory refFactory, long seed,
			TrackerDatum... data) {
		optimal = new OptimalTestCreator();

		//		testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest parameters ###############\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### cluster: " + cluster.toString() + "\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### refFactory: " + refFactory.toString() + "\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### finder: " + finder.toString() + "\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### reloadClasses: " + reloadClasses + "\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### data: " + data.length + "\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest parameters end ###############\n");

		// get Mersenne Twister random number
		logger.config("RandomTest: initializing MersenneTwisterFast with seed "
				+ seed);
		random = new MersenneTwisterFast(seed);

		this.cluster = cluster;
		this.refFactory = refFactory;

		this.finder = finder;
		this.reloadClasses = reloadClasses;
		this.data = data;
	}

	/**
	 * execute the operation to get the coverage
	 * @param ops
	 * @return
	 */
	protected Future<ElementManager<String, CoverageInformation>> execute(
			Operation[] ops) {
		Job<TestExecutorInput, ElementManager<String, CoverageInformation>, CoverageTestExecutor> ctx = CoverageTestExecutor
				.getContext(finder, new Test(cluster, refFactory, ops),
						reloadClasses, data);
		Future<ElementManager<String, CoverageInformation>> tmp = RunnerPool.getRunnerPool().execute(ctx);
		return tmp;
	}

	protected abstract void work(long duration);

	public final void test(long duration) {
		//		testful.evolutionary.Launcher.txtLog.logs.append("******************************************************\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("********** RandomTest test thread start **************\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("******** this is for initial population **************\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("******************************************************\n");

		startNotificationThread();

		work(duration);

		try {
			while (getRunningJobs() > 0)
				Thread.sleep(1000);
		} catch (InterruptedException e) {
		}

		keepRunning = false;
	}

	public ElementManager<String, CoverageInformation> getExecutionInformation() {
		return optimal.getCoverage();
	}

	public Iterable<TestCoverage> getOptimalTests() {
		return optimal.get();
	}

	public int getRunningJobs() {
		return tests.size();
	}

	private void startNotificationThread() {
		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				// this thread to initial population
				while (keepRunning) {
					try {
						if (tests.isEmpty()) {
							Thread.sleep(1000);
						} else {
							//							testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest startNotificationThread initial population ###############\n");
							//							testful.evolutionary.Launcher.txtLog.logs.append("size: " + tests.size() + "\n");

							// FIFO operate the queue for test cases
							Entry<Operation[], Future<ElementManager<String, CoverageInformation>>> entry = tests.take();

							//							Operation[] o = entry.getKey();
							//							testful.evolutionary.Launcher.txtLog.logs.append("Operation: ");
							//							for (Operation xx : o) {
							//								testful.evolutionary.Launcher.txtLog.logs.append(xx.toString() + "   ");
							//							}
							//							testful.evolutionary.Launcher.txtLog.logs.append("\n");

							ElementManager<String, CoverageInformation> cov = entry.getValue().get();

							//							testful.evolutionary.Launcher.txtLog.logs.append("\nCoverage: "+ cov.toString() + "\n");

							testsDone.incrementAndGet();
							numCall += entry.getKey().length;

							final TestCoverage testCoverage = new TestCoverage(
									new Test(cluster, refFactory, entry
											.getKey()), cov);
							//							testful.evolutionary.Launcher.txtLog.logs.append("### testCoverage:\n"+ testCoverage.toString() + "\n");
							optimal.update(testCoverage);
							//							testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest startNotificationThread initial population end #############\n");
						}
					} catch (InterruptedException e) {
						logger.log(Level.WARNING,
								"Interrupted: " + e.getMessage(), e);
						TestFul.debug(e);
					} catch (ExecutionException e) {
						logger.log(Level.WARNING,
								"Error during a test evaluation: " + e,
								e.getCause());
						TestFul.debug(e);
					}
				}
			}
		}, "futureWaiter");
		t.setDaemon(true);
		t.start();

		t = new Thread(new Runnable() {

			@Override
			public void run() {
				// log the records
				while (keepRunning) {
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						return;
					}

					long now = System.currentTimeMillis();

					optimal.log(null, numCall, (now - start));

					if (logger.isLoggable(Level.INFO)) {
						StringBuilder sb = new StringBuilder();

						long remaining = (stop - now) / 1000;

						sb.append(String.format(
								"Process: %5.2f%%, Time: %d:%02d left, ",
								(100.0 * (now - start)) / (stop - start),
								remaining / 60, remaining % 60));

						sb.append("Running ").append(getRunningJobs())
						.append(" jobs (").append(testsDone.get())
						.append(" done)\n");

						if (!optimal.getCoverage().isEmpty()) {
							sb.append("Coverage:\n");
							for (CoverageInformation info : optimal.getCoverage())
								sb.append("  ").append(info.getName())
								.append(": ").append(info.getQuality())
								.append("\n");
						}

						logger.info(sb.toString());
					}
				}
			}
		}, "notification");

		t.setDaemon(true);
		t.start();
	}

	/**
	 * @author Tudor
	 * @return The list of Tests(OpSequences) generated and selected by RT Note:
	 *         This function is useful only after it processes something :)
	 */
	public TestSuite getResults() {
		TestSuite ret = new TestSuite();
		//		testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest getResults ###############\n");
		//		testful.evolutionary.Launcher.txtLog.logs.append("### size:"+optimal.get().size()+"\n");
		for (TestCoverage test : optimal.get()) {
			//			testful.evolutionary.Launcher.txtLog.logs.append("############# optimal TestCoverage result:"+test.toString()+"\n");
			ret.add(test);
		}
		//		testful.evolutionary.Launcher.txtLog.logs.append("############# RandomTest getResults end ###############\n");
		return ret;
	}
}
